
#include "PreHeader.h"
#include "cLogicManager.h"

#include "cPlayerLogic.h"

#include "MsgGameLogic.h"
#include "cMsgManager.h"
#include "cLogicDataManager.h"
#include "cStageManager.h"

#include "cBullet.h"
#include "cEnemy.h"

//struct sRayCastCallBack : public b2RayCastCallback
//{
//	sRayCastCallBack()
//	{
//		fixture = NULL;
//	}
//
//	float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction)
//	{
//		this->fixture = fixture;
//
//		return fraction;
//	}
//
//	b2Fixture* fixture;
//};


cLogicManager::cLogicManager()
{
	m_b2World = NULL;
	m_playerLogic = NULL;
	m_stageManager = NULL;

	m_IDIssuer = INVALID_ID;

	m_score = 0;
	m_gameOver = false;
}

cLogicManager::~cLogicManager()
{
	RemoveAllEntity();

	CC_SAFE_DELETE(m_stageManager);
	CC_SAFE_DELETE(m_playerLogic);
	CC_SAFE_DELETE(m_b2World);
}

void cLogicManager::Init(const sLogicManagerDesc& desc)
{
	m_parent = desc.parent;

	// init phys
	b2Vec2 gravity(0.0f, 0.0f);
	m_b2World = new b2World(gravity, true);
	m_b2World->SetContactListener(this);

	// init player logic
	m_playerLogic = new cPlayerLogic;
	sPlayerLogicDesc plDesc;
	plDesc.logicManager = this;
	m_playerLogic->Init(plDesc);

	// init stage manager
	sStageManagerDesc mmDesc;
	mmDesc.parent = m_parent;
	mmDesc.logicManager = this;
	m_stageManager = new cStageManager;
	m_stageManager->Init(mmDesc);

	// init stage logic
	const nConstantFormat01::sConstant* constant = cLogicDataManager::it()->GetConstantData();
	m_scoreTimer.Init(constant->stage.decreaseScoreTick);
	SetStagePoint(constant->stage.startScore);
	m_stageManager->Start(1);
}

void cLogicManager::Update(tFloat dt)
{
	tInt velocityIterations = 8;
	tInt positionIterations = 1;

	m_b2World->Step(dt, velocityIterations, positionIterations);

	for (tEntityMap::iterator it = m_entityMap.begin(); it != m_entityMap.end(); )
	{
		tEntityMap::iterator itSave = it;
		it++;

		cEntity* entity = itSave->second;
		entity->Update(dt);

		if (entity->IsDeleted() == true)
		{
			m_b2World->DestroyBody(entity->GetBody());
			CC_SAFE_DELETE(entity);
			m_entityMap.erase(itSave);
		}
	}

	m_playerLogic->Update(dt);
	m_stageManager->Update(dt);
	
	if (m_scoreTimer.IsUsing() == true)
	{
		m_scoreTimer.Update(dt);
		if (m_scoreTimer.IsDone() == true)
		{
			const nConstantFormat01::sConstant* constant = cLogicDataManager::it()->GetConstantData();
			m_score -= constant->stage.decreaseScorePerTick;
			if (m_score <= 0)
			{
				GameOver();			
			}
			else
			{
				m_scoreTimer.Reset();
			}

			nGameLogic::sMSG_ScoreChanged info;
			info.score = m_score;
			cMsgManager::it()->SendMsg(MSG_TARGET_HUD_LAYER, info);
		}
	}
}

void cLogicManager::Draw()
{
	m_playerLogic->Draw();
}

void cLogicManager::Restart()
{
	m_gameOver = false;

	RemoveAllEntity();

	m_playerLogic->Reset();

	const nConstantFormat01::sConstant* constant = cLogicDataManager::it()->GetConstantData();
	m_scoreTimer.Init(constant->stage.decreaseScoreTick);
	SetStagePoint(constant->stage.startScore);
	m_stageManager->Start(1);
}

void cLogicManager::GameOver()
{
	m_scoreTimer.Clear();
	m_score = 0;
	m_gameOver = true;

	m_stageManager->Stop();

	nGameLogic::sMSG_GameOver info;
	cMsgManager::it()->SendMsg(MSG_TARGET_HUD_LAYER, info);
}

void cLogicManager::StageClear()
{
	m_scoreTimer.Clear();
	m_gameOver = true;

	m_stageManager->Stop();

	nGameLogic::sMSG_StageClear info;
	cMsgManager::it()->SendMsg(MSG_TARGET_HUD_LAYER, info);
}

cBullet* cLogicManager::CreateBullet(const sCreateBulletArg& arg)
{
	tID ID = GenerateID();
	
	// create physical body
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	bodyDef.position.Set(arg.pos.x/PTM_RATIO, arg.pos.y/PTM_RATIO);
	bodyDef.userData = (void*)ID;
	b2Body *body = m_b2World->CreateBody(&bodyDef);
	
	b2CircleShape circleShape;
	circleShape.m_radius = arg.radius/PTM_RATIO;
	
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &circleShape;	
	fixtureDef.density = 1.0f;
	//fixtureDef.friction = 0.5f;
	//fixtureDef.restitution = 0.5f;

	switch (arg.type)
	{
	case eBulletType::NORMAL:
	//case eBulletType::PIERCE:
		{
			fixtureDef.filter.categoryBits = 0x0002;
			fixtureDef.filter.maskBits = 0x0001;
			fixtureDef.filter.groupIndex = 2;
		}
		break;
	}
	body->CreateFixture(&fixtureDef);
	
	cBullet* bullet = new cBullet;
	sBulletDesc desc;
	desc.bulletType = arg.type;
	desc.entityDesc.ID = ID;
	desc.entityDesc.spriteFilename = arg.spriteFilename;
	desc.entityDesc.body = body;
	desc.entityDesc.parent = m_parent;
	bullet->Init(desc);
	
	// register
	m_entityMap.insert(std::make_pair(ID, bullet));

	return bullet;
}

void cLogicManager::CreateEnemy(const sCreateEnemyArg& arg)
{
	tID ID = GenerateID();

	// create body
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	bodyDef.position.Set(arg.pos.x/PTM_RATIO, arg.pos.y/PTM_RATIO);
	//bodyDef.linearDamping = 0.2f;
	bodyDef.userData = (void*)ID;
	b2Body *body = m_b2World->CreateBody(&bodyDef);

	b2CircleShape circleShape;
	circleShape.m_radius = arg.radius/PTM_RATIO;

	b2FixtureDef fixtureDef;
	fixtureDef.shape = &circleShape;
	fixtureDef.density = 50.0f;
	//fixtureDef.friction = 0.3f;
	fixtureDef.restitution = 0.5f;
	fixtureDef.isSensor = true;

	if (arg.type == eEnemyType::BREAKABLE)
	{
		fixtureDef.filter.categoryBits = 0x0001;
		fixtureDef.filter.maskBits = 0;
		fixtureDef.filter.groupIndex = 2;
	}
	else
	{
		fixtureDef.filter.categoryBits = 0;
		fixtureDef.filter.maskBits = 0;
		fixtureDef.filter.groupIndex = -1;
	}

	body->CreateFixture(&fixtureDef);

	cEnemy* enemy = new cEnemy;
	sEnemyDesc desc;
	desc.enemyType = arg.type;
	desc.foodID = arg.foodID;
	desc.speed = arg.speed;
	desc.entityDesc.ID = ID;
	desc.entityDesc.spriteFilename = arg.spriteFilename;
	desc.entityDesc.body = body;
	desc.entityDesc.parent = m_parent;
	enemy->Init(desc);

	// register
	m_entityMap.insert(std::make_pair(ID, enemy));
}

cEntity* cLogicManager::GetEntity(tID entityID)
{
	tEntityMap::iterator it = m_entityMap.find(entityID);
	if (it != m_entityMap.end())
	{
		return it->second;
	}

	return NULL;
}

void cLogicManager::AddStagePointByEat(tInt eatCount)
{
	const nConstantFormat01::sConstant* constant = cLogicDataManager::it()->GetConstantData();

	m_score += (eatCount * constant->stage.increaseScorePerEat);
	if (m_score > constant->stage.maxScore)
	{
		m_score = constant->stage.maxScore;
	}
	m_scoreTimer.Reset();

	nGameLogic::sMSG_ScoreChanged info;
	info.score = m_score;
	cMsgManager::it()->SendMsg(MSG_TARGET_HUD_LAYER, info);
}

void cLogicManager::SetStagePoint(tInt score)
{
	m_score = score;
	m_scoreTimer.Reset();

	nGameLogic::sMSG_ScoreChanged info;
	info.score = m_score;
	cMsgManager::it()->SendMsg(MSG_TARGET_HUD_LAYER, info);
}

void cLogicManager::Shoot(const CCPoint& lastPos)
{
	if (m_gameOver == true)
	{
		return;
	}

	m_playerLogic->Shoot(lastPos);
}

void cLogicManager::DetectCollision(const sDetectCollisionArg& arg, OUT tEntityDistanceInfoList& entityDistanceInfoList)
{
	struct sAABBQueryCallBack : public b2QueryCallback
	{
		sAABBQueryCallBack(const b2Vec2& playerPos, b2PolygonShape* shootShape, cLogicManager* logicManager)
		{
			this->playerPos = playerPos;
			this->shootShape = shootShape;
			this->logicManager = logicManager;
		}

		bool ReportFixture(b2Fixture* fixture)
		{
			b2Body* body = fixture->GetBody();
			b2Vec2 pos = body->GetPosition();

			b2Vec2 direction = pos - playerPos;
			tFloat distance = direction.Length();

			if (fixture->GetShape()->m_type != b2Shape::e_circle)
			{
				return true;
			}

			b2CircleShape* circleShape = (b2CircleShape*)fixture->GetShape();
			b2Manifold manifold;
			b2Transform shootShapeTransform;
			shootShapeTransform.SetIdentity();

			b2CollidePolygonAndCircle(&manifold, shootShape, shootShapeTransform, circleShape, body->GetTransform());

			if (manifold.pointCount <= 0)
			{
				return true;
			}

			tID entityID = (tID)body->GetUserData();
			cEntity* entity = logicManager->GetEntity(entityID);
			if (entity == NULL || entity->GetEntityType() == eEntityType::BULLET)
			{
				return true;
			}

			sEntityDistanceInfo info;
			info.entityID = entityID;
			info.distance = distance*PTM_RATIO;
			distanceInfoList.push_back(info);

			return true;
		}

		tEntityDistanceInfoList distanceInfoList; 
		b2Vec2 playerPos;
		b2PolygonShape* shootShape;
		cLogicManager* logicManager;
	};

	tFloat distance = arg.distance/PTM_RATIO;
	tFloat width = arg.width/PTM_RATIO;

	b2Vec2 startPos = CCP_TO_B2VEC2(arg.playerPos);
	b2Vec2 direction(arg.direction.x, arg.direction.y);
	b2Vec2 centerPoint = startPos + ((distance*0.5f)*direction);
	
	b2PolygonShape poly;
	poly.SetAsBox(width, distance*0.5f, centerPoint, arg.angle);

	b2AABB aabb;
	b2Transform transform;
	transform.SetIdentity();
	poly.ComputeAABB(&aabb, transform);

	sAABBQueryCallBack callBack(startPos, &poly, this);
	m_b2World->QueryAABB(&callBack, aabb);

	if (callBack.distanceInfoList.empty() == false)
	{
		callBack.distanceInfoList.sort();
		entityDistanceInfoList = callBack.distanceInfoList;
	}
}

void cLogicManager::BeginContact(b2Contact* contact)
{
	b2Fixture* fixtureA = contact->GetFixtureA();
	b2Fixture* fixtureB = contact->GetFixtureB();

	tID entityIDA = (tID)fixtureA->GetBody()->GetUserData();
	tID entityIDB = (tID)fixtureB->GetBody()->GetUserData();

	cEntity* entityA = GetEntity(entityIDA);
	cEntity* entityB = GetEntity(entityIDB);

	if (entityA == NULL || entityB == NULL)
	{
		return;
	}

	entityA->OnHit();
	entityB->OnHit();
}

void cLogicManager::RemoveAllEntity()
{
	for (tEntityMap::iterator it = m_entityMap.begin(); it != m_entityMap.end(); it++)
	{
		cEntity* entity = it->second;
		m_b2World->DestroyBody(entity->GetBody());
		CC_SAFE_DELETE(entity);
	}
	m_entityMap.clear();
}

BEGIN_MSG_MAP(cLogicManager, iMsgTargetBase)
	MSG_MAP(nGameLogic, MSG_Restart, Handle)
END_MSG_MAP()

void cLogicManager::Handle(const nGameLogic::sMSG_Restart& msg)
{
	Restart();
}
